﻿using Minecraft_Launcher.Properties;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;

namespace Minecraft_Launcher
{
    public partial class Main : Form
    {
        private static JArray versArr;
        private static string version;
        private static string assetsVer;

        private static WebClient libsDown = new WebClient();
        private static WebClient assetsDown = new WebClient();

        private static int downCount;

        private static List<string> downList = new List<string>();

        private static List<string> libsList = new List<string>();
        private static List<string> assetsList = new List<string>();

        private Process process;

        private static string username;
        private static string accesstoken;
        private static string uuid;

        public Main()
        {
            InitializeComponent();
            InitializeVersion();
        }

        private void InitializeVersion()
        {
            versionList.Items.Clear();
            versionList.Items.Add("Latest Version");
            versionList.SelectedIndex = 0;

            StreamReader reader = new StreamReader(new WebClient().OpenRead("http://s3.amazonaws.com/Minecraft.Download/versions/versions.json"));
            versArr = JArray.Parse(JObject.Parse(reader.ReadToEnd())["versions"].ToString());
            reader.Close();
            
            foreach (JObject itemObj in versArr)
            {
                switch ((itemObj["type"].ToString()))
                {
                    case "release":
                        versionList.Items.Add(itemObj["id"].ToString());
                        break;
                    case "snapshot":
                        if (checkDev.Checked)
                            versionList.Items.Add(itemObj["id"].ToString());
                        break;
                    case "old_beta":
                        if (checkBeta.Checked)
                            versionList.Items.Add(itemObj["id"].ToString());
                        break;
                    case "old_alpha":
                        if (checkAlpha.Checked)
                            versionList.Items.Add(itemObj["id"].ToString());
                        break;
                }
            }
        }

        private static bool IsLoginSuccess(ref string username, string password, ref string accesstoken, ref string uuid)
        {
            try
            {
                string request = "{\"agent\":{\"name\":\"Minecraft\",\"version\":1},\"username\":\"" + username + "\",\"password\":\"" + password + "\"}";

                SslStream stream = new SslStream(new TcpClient("authserver.mojang.com", 443).GetStream());
                stream.AuthenticateAsClient("authserver.mojang.com");

                List<string> http_request = new List<string>()
                {
                    "POST /authenticate HTTP/1.1",
                    "Host: authserver.mojang.com",
                    "Content-Type: application/json",
                    "Content-Length: " + Encoding.ASCII.GetBytes(request).Length,
                    "Connection: close",
                    "",
                    request
                };
                stream.Write(Encoding.ASCII.GetBytes(string.Join("\r\n", http_request.ToArray())));

                StreamReader reader = new StreamReader(stream);
                string raw_result = reader.ReadToEnd();
                reader.Close();

                if (raw_result.StartsWith("HTTP/1.1"))
                {
                    if (int.Parse(raw_result.Split(' ')[1]) == 200)
                    {
                        string result = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4);
                        string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries);
                        if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; }
                        temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries);
                        if (temp.Length >= 2) { username = temp[1].Split('"')[0]; }
                        temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries);
                        if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; }
                        return (result.Contains("availableProfiles\":[]}")) ? false : true;
                    }
                }
                return false;
            }
            catch { return false; }
        }

        private void login_Click(object sender, EventArgs e)
        {
            groupBox1.Enabled = false;
            groupBox2.Enabled = false;
            groupBox3.Enabled = false;

            username = Email.Text;

            if (IsLoginSuccess(ref username, password.Text, ref accesstoken, ref uuid))
                gameDownload(username, accesstoken, uuid);
        }

        private void gameDownload(string username, string accesstoken, string uuid)
        {
            version = (versionList.SelectedIndex == 0) ? versArr[0]["id"].ToString() : versionList.Text;
            string url = "http://s3.amazonaws.com/Minecraft.Download/versions/" + version + "/" + version + ".";
            string dir = AppDomain.CurrentDomain.BaseDirectory + "version\\" + version;

            if (!(Directory.Exists(dir))) Directory.CreateDirectory(dir);

            if (File.Exists(dir + "\\" + version + ".jar"))
            {
                if (File.Exists(dir + "\\" + version + ".json"))
                    InitializeLibraries(dir);
                else gameDownload1(dir, url);
            }
            else
            {
                WebClient jarDown = new WebClient();
                jarDown.DownloadFileAsync(new Uri(url + "jar"), dir + "\\" + version + ".jar", dir + "|" + url);
                console.AppendText(Environment.NewLine + "Attempting to download " + dir + "\\" + version + ".jar");
                jarDown.DownloadProgressChanged += DownloadProgressChanged;
                jarDown.DownloadFileCompleted += jarDown_DownloadFileCompleted;
            }
        }

        private void gameDownload1(string dir, string url)
        {
            WebClient jsonDown = new WebClient();
            jsonDown.DownloadFileAsync(new Uri(url + "json"), dir + "\\" + version + ".json", dir);
            console.AppendText(Environment.NewLine + "Attempting to download " + dir + "\\" + version + ".json");
            jsonDown.DownloadProgressChanged += DownloadProgressChanged;
            jsonDown.DownloadFileCompleted += jsonDown_DownloadFileCompleted;
        }

        private void jsonDown_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            string dir = (string)e.UserState;
            console.AppendText(Environment.NewLine + "Attempting to download " + dir + "\\" + version + ".json");
            InitializeLibraries((string)e.UserState);
        }

        private void jarDown_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            string dir = ((string)e.UserState).Split('|')[0];
            console.AppendText(Environment.NewLine + "Finished download " + dir + "\\" + version + ".jar");
            if (!(File.Exists(dir + "\\" + version + ".json")))
                gameDownload1(dir, ((string)e.UserState).Split('|')[1]);
        }

        private string CheckOS()
        {
            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Unix:
                    return (Directory.Exists("/Applications") & Directory.Exists("/System") & Directory.Exists("/Users") & Directory.Exists("/Volumes")) ? "osx" : "linux";
                case PlatformID.MacOSX:
                    return "osx";
                default:
                    return "windows";
            }
        }

        private void InitializeLibraries(string verDir)
        {
            StreamReader reader = new StreamReader(verDir + "\\" + version + ".json");
            JArray jArray = JArray.Parse(JObject.Parse(reader.ReadToEnd())["libraries"].ToString());
            reader.Close();
            int count = jArray.Count;
            foreach (JObject itemObj in jArray)
            {
                string pack = itemObj["name"].ToString().Split(':')[0];
                string name = itemObj["name"].ToString().Split(':')[1];
                string ver = itemObj["name"].ToString().Split(':')[2];
                string fileName = name + "-" + ver;

                if (itemObj.ToString().Contains("natives"))
                {
                    fileName += "-" + itemObj["natives"][CheckOS()];
                    fileName = fileName.Replace("${arch}", (Environment.Is64BitOperatingSystem) ? "64" : "32");
                }
                fileName += ".jar";

                string url = "https://libraries.minecraft.net/" + pack.Replace('.', '/') + "/" + name + "/" + ver + "/" + fileName;

                if (itemObj.ToString().Contains("rules"))
                {
                    foreach (JObject itemRules in itemObj["rules"])
                    {
                        if (itemRules.ToString().Contains("os"))
                        {
                            if ((itemRules["action"].ToString() == "allow" && itemRules["os"]["name"].ToString() == CheckOS()) || (itemRules["action"].ToString() == "disallow" && itemRules["os"]["name"].ToString() != CheckOS()))
                            {
                                downList.Add(url + '|' + fileName);
                                libsList.Add(AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + fileName);
                            }
                        }
                    }
                }
                else
                {
                    downList.Add(url + '|' + fileName);
                    libsList.Add(AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + fileName);
                }
            }

            if (!(Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "libraries")))
                Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + "libraries");

            LibrariesDownload();
        }

        private void LibrariesDownload()
        {
            if (File.Exists(AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + downList[0].Split('|')[1]))
                LibrairesDownload1();
            else
            {
                string url = downList[0].Split('|')[0];
                string name = downList[0].Split('|')[1];

                libsDown.DownloadFileAsync(new Uri(url), AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + name, name);
                console.AppendText(Environment.NewLine + "Attempting to download " + AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + name);
            }
            libsDown.DownloadProgressChanged += DownloadProgressChanged;
            libsDown.DownloadFileCompleted += libsDown_DownloadFileCompleted;
        }

        private void libsDown_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            console.AppendText(Environment.NewLine + "Finished downloading " + AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + (string)e.UserState);
            LibrairesDownload1();
        }

        private void LibrairesDownload1()
        {
            try
            {
                downCount++;

                if (File.Exists(AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + downList[downCount].Split('|')[1]))
                    LibrairesDownload1();
                else
                {
                    string url = downList[downCount].Split('|')[0];
                    string name = downList[downCount].Split('|')[1];

                    libsDown.DownloadFileAsync(new Uri(url), AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + name, name);
                    console.AppendText(Environment.NewLine + "Attempting to download " + AppDomain.CurrentDomain.BaseDirectory + "libraries\\" + name);
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                downCount = 0;
                downList.Clear();
                InitializeIndex();
            }
        }

        private void InitializeIndex()
        {
            string indexDir = AppDomain.CurrentDomain.BaseDirectory + "assets\\indexes";
            StreamReader reader = new StreamReader(AppDomain.CurrentDomain.BaseDirectory + "version\\" + version + "\\" + version + ".json");
            assetsVer = JObject.Parse(reader.ReadToEnd())["assets"].ToString();
            reader.Close();

            if (!(Directory.Exists(indexDir)))
                Directory.CreateDirectory(indexDir);
            if (!(File.Exists(indexDir + "\\" + assetsVer + ".json")))
            {
                WebClient indexDown = new WebClient();
                indexDown.DownloadFileAsync(new Uri("https://s3.amazonaws.com/Minecraft.Download/indexes/" + assetsVer + ".json"), indexDir + "\\" + assetsVer + ".json", indexDir + "\\" + assetsVer + ".json");
                indexDown.DownloadProgressChanged += DownloadProgressChanged;
                indexDown.DownloadFileCompleted += indexDown_DownloadFileCompleted;
            }
            else InitializeAssets(indexDir + "\\" + assetsVer + ".json");
        }

        private void indexDown_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            InitializeAssets((string)e.UserState);
        }

        private void InitializeAssets(string indexDir)
        {
            StreamReader reader = new StreamReader(indexDir);
            JObject obj = JObject.Parse(reader.ReadToEnd());
            reader.Close();
            downList.Clear();
            int count = 0;
            foreach (JProperty itemCount in obj["objects"]) count++;
            foreach(JProperty itemObj in obj["objects"])
            {
                string hash = itemObj.ToString().Substring(itemObj.ToString().IndexOf(": \"") + ": \"".Length).Split('"')[0];
                string hash1 = hash.Substring(0, 2);
                string url = "http://resources.download.minecraft.net/" + hash1 + "/" + hash;
                assetsList.Add(url + '|' + hash);
            }

            if (!(Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "assets\\objects")))
                Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + "assets\\objects");

            downList = assetsList;
            AssetsDownload();
        }

        private void AssetsDownload()
        {
            string url = downList[0].Split('|')[0];
            string name = downList[0].Split('|')[1];
            string hash = name.Substring(0, 2);

            if (File.Exists(AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash + "\\" + downList[downCount].Split('|')[1]))
                AssetsDownload1();
            else
            {
                if (!(Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash)))
                    Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash);

                assetsDown.DownloadFileAsync(new Uri(url), AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash + "\\" + name, name);
                assetsDown.DownloadProgressChanged += DownloadProgressChanged;
                assetsDown.DownloadFileCompleted += assetsDown_DownloadFileCompleted;

                console.AppendText(Environment.NewLine + "Attempting to download " + AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash + "\\" + name);
            }
        }

        private void assetsDown_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            string name = (string)e.UserState;
            console.AppendText(Environment.NewLine + "Finished downloading " + AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + name.Substring(0, 2) + "\\" + name);
            AssetsDownload1();
        }

        private void AssetsDownload1()
        {
            try
            {
                downCount++;
                string URL = downList[downCount].Split('|')[0];
                string FileName = downList[downCount].Split('|')[1];
                string hash = FileName.Substring(0, 2);

                if (File.Exists(AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash + "\\" + downList[downCount].Split('|')[1]))
                    AssetsDownload1();
                else
                {
                    if (!(Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash)))
                        Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash);

                    assetsDown.DownloadFileAsync(new Uri(URL), AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash + "\\" + FileName, FileName);
                    console.AppendText(Environment.NewLine + "Attempting to download " + AppDomain.CurrentDomain.BaseDirectory + "assets\\objects\\" + hash + "\\" + FileName);
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                downCount = 0;
                downList.Clear();
                DownloadNatives();
            }
        }

        private void DownloadResource(string dir, byte[] resource)
        {
            if (!(File.Exists(dir)))
                File.WriteAllBytes(dir, resource);
        }

        private void DownloadNatives()
        {
            if (!(Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "natives")))
                Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + "natives");

            switch (CheckOS())
            {
                case "windows":
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\jinput_dx8.dll", Resources.jinput_dx8);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\jinput_dx8_64.dll", Resources.jinput_dx8_64);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\jinput_raw.dll", Resources.jinput_raw);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\jinput_raw_64.dll", Resources.jinput_raw_64);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\lwjgl.dll", Resources.lwjgl);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\lwjgl64.dll", Resources.lwjgl64);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\OpenAL32.dll", Resources.OpenAL32);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\OpenAL64.dll", Resources.OpenAL64);
                    break;
                case "linux":
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\libjinput_linux.so", Resources.libjinput_linux);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\libinput_linux64.so", Resources.libjinput_linux64);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\liblwjgl.so", Resources.liblwjgl);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\liblwjgl64.so", Resources.liblwjgl64);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\libopenal.so", Resources.libopenal);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\libopenal64.so", Resources.libopenal64);
                    break;
                case "osx":
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\libjinput_osx.dylib", Resources.libjinput_osx);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\liblwjgl.dylib", Resources.liblwjgl1);
                    DownloadResource(AppDomain.CurrentDomain.BaseDirectory + @"natives\openal.dylib", Resources.openal);
                    break;
            }
            MinecraftStart();
        }

        private void MinecraftStart()
        {
            if (!(Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "\\natives")))
                Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + "\\natives");

            string arguments = "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump " + this.arguments.Text + " -Djava.library.path=\"" + AppDomain.CurrentDomain.BaseDirectory + @"\natives"" -cp """;

            foreach (string item in libsList)
                arguments += item + ';';
            arguments += AppDomain.CurrentDomain.BaseDirectory + "version\\" + version + "\\" + version + ".jar\" net.minecraft.client.main.Main --username " + username +  " --version " + version + " --gameDir \"" + Application.StartupPath + @""" --assetsDir """ + AppDomain.CurrentDomain.BaseDirectory + "assets\" --assetIndex " + assetsVer + " --accessToken " + accesstoken + " --uuid " + uuid + " --userProperties {}";

            process = new Process()
            {
                EnableRaisingEvents = true,
                StartInfo = new ProcessStartInfo()
                {
                    FileName = (javaCheckBox.Checked) ? javaDir.Text : "java.exe",
                    Arguments = arguments,
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true
                }
            };

            process.Start();
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();

            console.Clear();

            process.OutputDataReceived += process_OutputDataReceived;
            process.Exited += process_Exited;
        }

        private void process_Exited(object sender, EventArgs e)
        {
            groupBox1.Enabled = true;
            groupBox2.Enabled = true;
            groupBox3.Enabled = true;
        }

        private void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            CheckForIllegalCrossThreadCalls = false;
            console.AppendText(Environment.NewLine + e.Data);
        }

        private void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            progressDown.Value = e.ProgressPercentage;
        }

        private void checkDev_CheckedChanged(object sender, EventArgs e)
        {
            InitializeVersion();
        }

        private void checkBeta_CheckedChanged(object sender, EventArgs e)
        {
            InitializeVersion();
        }

        private void checkAlpha_CheckedChanged(object sender, EventArgs e)
        {
            InitializeVersion();
        }

        private void javaCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            javaDir.Enabled = javaCheckBox.Checked;
        }

        private void arguCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            arguments.Enabled = arguCheckBox.Checked;
        }

        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            e.Link.LinkData = "https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md";
            Process.Start(e.Link.LinkData.ToString());
        }
    }
}
